﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using System.Net;
using System.Net.Cache;
using System.Threading;

namespace ForumReader
{
    public class IchigoForumClient : ForumClient
    {
        public IchigoForumClient()
        {
        }

        public string ForumTitle
        {
            get { return "森野苺のニュース速報"; }
        }

        private const string BaseAddress = "http://www.ichigo-diary.com";

        private static string DownloadPage(string url, CancellationToken ct)
        {
            using (WebClient client = new WebClient())
            {
                client.BaseAddress = BaseAddress;
                client.CachePolicy = new RequestCachePolicy(RequestCacheLevel.CacheIfAvailable);
                client.Encoding = Encoding.UTF8;
                client.Headers.Add(HttpRequestHeader.UserAgent,
                    "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0");
                return client.DownloadString(url);
            }
        }

        private static DateTime ExtractDateTime(Match m, int startIndex)
        {
            int YY = Int32.Parse(m.Groups[startIndex + 0].Value);
            int MM = Int32.Parse(m.Groups[startIndex + 1].Value);
            int DD = Int32.Parse(m.Groups[startIndex + 2].Value);
            int hh = Int32.Parse(m.Groups[startIndex + 3].Value);
            int mm = Int32.Parse(m.Groups[startIndex + 4].Value);
            int ss = Int32.Parse(m.Groups[startIndex + 5].Value);
            return new DateTime(YY, MM, DD, hh, mm, ss,
                DateTimeKind.Utc).AddHours(-9); // tokyo time
        }

        public IEnumerable<ForumBoard[]> GetBoards(CancellationToken ct)
        {
            string html = DownloadPage("/", ct);
            yield return ParseSpecialBoards(html);
            yield return ParseNormalBoards(html);
        }

        private static ForumBoard[] ParseSpecialBoards(string html)
        {
            ForumBoard board = new ForumBoard();
            board.Id = ".POPULAR";
            board.Name = "人気記事";
            board.Title = "人気記事";
            board.ThreadCount = -1;
            return new ForumBoard[] { board };
        }

        private static ForumBoard[] ParseNormalBoards(string html)
        {
            List<ForumBoard> boards = new List<ForumBoard>();
            foreach (Match m in rgxContents.Matches(html))
            {
                string table = m.Groups[1].Value;
                foreach (Match mBoard in rgxBoard.Matches(table))
                {
                    ForumBoard board = new ForumBoard
                    {
                        Id = mBoard.Groups[1].Value,
                        Name = mBoard.Groups[1].Value,
                        Title = mBoard.Groups[1].Value,
                        ThreadCount = Int32.Parse(mBoard.Groups[2].Value),
                    };
                    boards.Add(board);
                }
            }
            return boards.ToArray();
        }

        // <table class="menu_left" ... </table>
        private static Regex rgxContents = new Regex(
            @"<table class=""menu_left"".*?>(.*?)</table>",
            RegexOptions.Singleline);

        // <a href="index.php?mode=&kiji_category=2.5次元画像" target="_self">
        // 2.5次元画像(7)</a>
        private static Regex rgxBoard = new Regex(
            @"<a href=""index.php[^""]*kiji_category=([^""]*)"".*?\((\d+)\)</a>",
            RegexOptions.Singleline);

        public IEnumerable<ForumThread[]> GetThreads(string boardId, CancellationToken ct)
        {
            if (boardId == ".POPULAR")
            {
                string html = DownloadPage("/", ct);
                yield return ParsePopularThreads(html);
            }
            else
            {
                string url = "/index.php?mode=&kiji_category=" + boardId;
                string html = DownloadPage(url, ct);
                yield return ParseThreads(html);
            }
        }

        private static ForumThread[] ParsePopularThreads(string html)
        {
            List<ForumThread> threads = new List<ForumThread>();

            int startIndex = html.IndexOf(@"<img src=""img/bar0002.jpg""");
            if (startIndex < 0)
                return threads.ToArray();

            foreach (Match m in rgxPopular.Matches(html, startIndex))
            {
                ForumThread thread = new ForumThread
                {
                    Id = m.Groups[1].Value,
                    Title = m.Groups[2].Value,
                    Date = ExtractDateTime(m, 3),
                    Link = BaseAddress + "/kiji/" + m.Groups[1].Value + ".html",
                };
                threads.Add(thread);
            }
            return threads.ToArray();
        }

        // <a href="kiji/1351240517.html" target="_blank"><center>
        // <img src="kiji/1351240517/thumbnail_blog.jpg"></center></a>
        // <font size="2">15.<a href="kiji/1351240517.html" target="_blank">
        // ...title here...</a></font>
        // <font size="2" color="#777777"> - 2012年10月26日 01時35分17秒</font>
        // <br><font size="2" color="#FFFFFF">(エロ写メ・自撮り画像)</font><br><br>
        private static Regex rgxPopular = new Regex(
            @"<font size=""2"">\d+[.]<a href=""kiji/(\d+)[.]html""[^>]*>(.*?)</a>" +
            @".*?<font [^>]*> - (\d+)年(\d+)月(\d+)日 (\d+)時(\d+)分(\d+)秒",
            RegexOptions.Singleline);

        private static ForumThread[] ParseThreads(string html)
        {
            List<ForumThread> threads = new List<ForumThread>();

            // Extract the main text part.
            Match mBody = rgxBody.Match(html);
            if (!mBody.Success)
                return threads.ToArray();
            html = html.Substring(mBody.Index);

            // Parse threads.
            foreach (Match m in rgxThread.Matches(html))
            {
                ForumThread thread = new ForumThread
                {
                    Id = m.Groups[1].Value,
                    Title = m.Groups[2].Value,
                    Date = ExtractDateTime(m, 3),
                    Link = BaseAddress + "/kiji/" + m.Groups[1].Value + ".html",
                };
                threads.Add(thread);
            }
            return threads.ToArray();
        }

        // </form>...<meta name="GENERATOR" content="JustSystems Homepage ...
        private static Regex rgxBody = new Regex(
            @"</form>.*?<meta name=""GENERATOR"".*?<table(.*?)</table>",
            RegexOptions.Singleline);

        // <td valign="top" align="center" width="183">
        // <a href="kiji/1350688474.html" target="_blank">
        // <img src="kiji/1350688474/thumbnail_blog.jpg" border="0">
        // <br><font size="2">Title here</font>
        // <font size="2" color="#777777"> - 2012年10月19日 16時14分34秒</font></a></td>
        private static Regex rgxThread = new Regex(
            // @"<td [^>]*>"
            @"<a href=""kiji/(\d+)[.]html""[^>]*>" +
            @"<img [^>]*><br><font size=""2"">(.*?)</font>" +
            @"<font [^>]*>.*?(\d+)年(\d+)月(\d+)日 (\d+)時(\d+)分(\d+)秒",
            RegexOptions.Singleline);

        // <div id=r_761 class=rs>761 : <span class=nam>名無し＠AABB</span>
        // [] : 2012/09/29(土) 11:30:59.24 ID:wT0IQMgb0</div><p class=bd>...</p>
        private static Regex rgxPost = new Regex(
            @"<div id=r_\d+ class=rs>" +
            @".*?<span class=nam>(.*?)</span>" +
            @".*?(\d\d\d\d)/(\d\d)/(\d\d).*?(\d\d):(\d\d):(\d\d)" +
            @".*?</div><p class=bd>(.*?)</p>",
            RegexOptions.Singleline);

        // <object ... value="file=http://cache.minkch.net/movie/12345_67_8_9.mp4">
        // ...</object>
        private static Regex rgxVideo = new Regex(
            @"<object .*?value=""file=([^""]*)""",
            RegexOptions.Singleline);

        public IEnumerable<ForumPost[]> GetPosts(string threadId, CancellationToken ct)
        {
            string url = "/kiji/" + threadId + ".html";
            string html = DownloadPage(url, ct);
            yield return ParsePosts(html);
        }

        private static ForumPost[] ParsePosts(string html)
        {
            List<ForumPost> posts = new List<ForumPost>();

            // Extract the main text part.
            Match mBody = rgxBody.Match(html);
            if (!mBody.Success)
                return posts.ToArray();
            html = mBody.Groups[1].Value;

            // Parse posts and record the start indices of embedded images
            // and videos.
            HashSet<int> ignoreIndices = new HashSet<int>();
            foreach (Match m in rgxPost.Matches(html))
            {
                ForumPost post = new ForumPost();
                post.Author = m.Groups[1].Value;
                post.Date = ExtractDateTime(m, 2);

                // Extract images and videos.
                string body = m.Groups[8].Value;
                post.Attachments = ParseImages(body, m.Groups[8].Index, ignoreIndices).ToArray();
                //post.Videos = (from Match mVideo in rgxVideo.Matches(body)
                //               select mVideo.Groups[1].Value).ToArray<string>();

                posts.Add(post);
            }

            // Put images and videos that don't belong to any post to a single
            // "default" post.
            ForumAttachment[] otherAtt = ParseImages(html, 0, ignoreIndices).ToArray();
            if (otherAtt.Length > 0)
            {
                ForumPost post = new ForumPost
                {
                    Author = null,
                    Date = DateTime.MinValue,
                    Attachments = otherAtt,
                };
                posts.Add(post);
            }
            return posts.ToArray();
        }

        // Parses images in a post. Each image is enclosed in a hyperlink.
        // If the hyperlink looks like a bigger version of the thumbnail,
        // the hyperlink is returned. Otherwise, the thumbnail is returned.
        private static IEnumerable<ForumAttachment> ParseImages(
            string html, int baseIndex, HashSet<int> ignoreIndices)
        {
            foreach (Match m in rgxImage.Matches(html))
            {
                if (ignoreIndices.Contains(baseIndex + m.Index))
                    continue;
                ignoreIndices.Add(baseIndex + m.Index);

                string big = m.Groups[1].Value;
                string small = m.Groups[2].Value;
                string url = small;
                if (small.IndexOf('/') == big.IndexOf('/') && !big.EndsWith("."))
                {
                    string ext = big.Substring(big.LastIndexOf('.') + 1);
                    if (IsSupportedImageExtension(ext.ToLowerInvariant()))
                        url = big;
                }
                yield return new ForumAttachment
                {
                    Source = BaseAddress + "/kiji/" + url,
                    Thumbnail = BaseAddress + "/kiji/" + url,
                    Type = ForumAttachmentType.Image,
                };
            }
        }

        // <a href="1349232198/img035b.jpg" target=_blank>
        // <img src="1349232198/img014.jpg"></a>
        private static Regex rgxImage = new Regex(
            @"<a\s+href=""([^""]*)""[^>]*><img src=""([^""]*)""></a>",
            RegexOptions.Singleline);

        private static bool IsSupportedImageExtension(string ext)
        {
            switch (ext)
            {
                case "jpg":
                case "jpeg":
                case "gif":
                case "bmp":
                case "png":
                    return true;
            }
            return false;
        }
    }
}
